/**
 * 
 */
package com.android.fxcontacts;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.pim.vcard.VCardComposer;
import android.text.TextUtils;
import android.util.Log;

/**
 * Class for exporting vCard.
 * 
 * Note that this Activity assumes that the instance is a "one-shot Activity",
 * which will be
 * finished (with the method {@link Activity#finish()}) after the export and
 * never reuse
 * any Dialog in the instance. So this code is careless about the management
 * around managed
 * dialogs stuffs (like how onCreateDialog() is used).
 * 
 */
public class ExportVCardActivity extends Activity
{
	private static final String LOG_TAG = "ExportVCardActivity";

	// If true, VCardExporter is able to emits files longer than 8.3 format.
	private static final boolean ALLOW_LONG_FILE_NAME = false;
	private String mTargetDirectory;
	private String mFileNamePrefix;
	private String mFileNameSuffix;
	private int mFileIndexMinimum;
	private int mFileIndexMaximum;
	private String mFileNameExtension;
	private String mVCardTypeStr;
	private Set<String> mExtensionsToConsider;

	private ProgressDialog mProgressDialog;
	private String mExportingFileName;

	private Handler mHandler = new Handler();

	// Used temporaly when asking users to confirm the file name
	private String mTargetFileName;

	// String for storing error reason temporaly.
	private String mErrorReason;

	private ActualExportThread mActualExportThread;

	private class CancelListener implements DialogInterface.OnClickListener,
			DialogInterface.OnCancelListener
	{
		public void onClick(DialogInterface dialog, int which)
		{
			finish();
		}

		public void onCancel(DialogInterface dialog)
		{
			finish();
		}
	}

	private CancelListener mCancelListener = new CancelListener();

	private class ErrorReasonDisplayer implements Runnable
	{
		private final int mResId;

		public ErrorReasonDisplayer(int resId)
		{
			mResId = resId;
		}

		public ErrorReasonDisplayer(String errorReason)
		{
			mResId = R.id.dialog_fail_to_export_with_reason;
			mErrorReason = errorReason;
		}

		public void run()
		{
			// Show the Dialog only when the parent Activity is still alive.
			if (!ExportVCardActivity.this.isFinishing())
			{
				showDialog(mResId);
			}
		}
	}

	private class ExportConfirmationListener implements
			DialogInterface.OnClickListener
	{
		private final String mFileName;

		public ExportConfirmationListener(String fileName)
		{
			mFileName = fileName;
		}

		public void onClick(DialogInterface dialog, int which)
		{
			if (which == DialogInterface.BUTTON_POSITIVE)
			{
				mActualExportThread = new ActualExportThread(mFileName);
				showDialog(R.id.dialog_exporting_vcard);
			}
		}
	}

	private class ActualExportThread extends Thread implements
			DialogInterface.OnCancelListener
	{
		private PowerManager.WakeLock mWakeLock;
		private boolean mCanceled = false;

		public ActualExportThread(String fileName)
		{
			mExportingFileName = fileName;
			PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
			mWakeLock = powerManager.newWakeLock(
					PowerManager.SCREEN_DIM_WAKE_LOCK
							| PowerManager.ON_AFTER_RELEASE, LOG_TAG);
		}

		@Override
		public void run()
		{
			boolean shouldCallFinish = true;
			mWakeLock.acquire();
			VCardComposer composer = null;
			try
			{
				OutputStream outputStream = null;
				try
				{
					outputStream = new FileOutputStream(mExportingFileName);
				} catch (FileNotFoundException e)
				{
					final String errorReason = getString(
							R.string.fail_reason_could_not_open_file,
							mExportingFileName, e.getMessage());
					shouldCallFinish = false;
					mHandler.post(new ErrorReasonDisplayer(errorReason));
					return;
				}

				composer = new VCardComposer(ExportVCardActivity.this,
						mVCardTypeStr, true);
				/*
				 * int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
				 * VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
				 * composer = new VCardComposer(ExportVCardActivity.this,
				 * vcardType, true);
				 */

				composer.addHandler(composer.new HandlerForOutputStream(
						outputStream));

				if (!composer.init())
				{
					final String errorReason = composer.getErrorReason();
					Log.e(LOG_TAG, "initialization of vCard composer failed: "
							+ errorReason);
					final String translatedErrorReason = translateComposerError(errorReason);
					mHandler.post(new ErrorReasonDisplayer(getString(
							R.string.fail_reason_could_not_initialize_exporter,
							translatedErrorReason)));
					shouldCallFinish = false;
					return;
				}

				int size = composer.getCount();

				if (size == 0)
				{
					mHandler
							.post(new ErrorReasonDisplayer(
									getString(R.string.fail_reason_no_exportable_contact)));
					shouldCallFinish = false;
					return;
				}

				mProgressDialog
						.setProgressNumberFormat(getString(R.string.exporting_contact_list_progress));
//				setProgressNumberFormat(mProgressDialog.getClass(),getString(R.string.exporting_contact_list_progress));
				mProgressDialog.setMax(size);
				mProgressDialog.setProgress(0);

				while (!composer.isAfterLast())
				{
					if (mCanceled)
					{
						return;
					}
					if (!composer.createOneEntry())
					{
						final String errorReason = composer.getErrorReason();
						Log.e(LOG_TAG, "Failed to read a contact: "
								+ errorReason);
						final String translatedErrorReason = translateComposerError(errorReason);
						mHandler
								.post(new ErrorReasonDisplayer(
										getString(
												R.string.fail_reason_error_occurred_during_export,
												translatedErrorReason)));
						shouldCallFinish = false;
						return;
					}
					mProgressDialog.incrementProgressBy(1);
				}
			} finally
			{
				if (composer != null)
				{
					composer.terminate();
				}
				mWakeLock.release();
				mProgressDialog.dismiss();
				if (shouldCallFinish && !isFinishing())
				{
					finish();
				}
			}
		}

		@Override
		public void finalize()
		{
			if (mWakeLock != null && mWakeLock.isHeld())
			{
				mWakeLock.release();
			}
		}

		public void cancel()
		{
			mCanceled = true;
		}

		public void onCancel(DialogInterface dialog)
		{
			cancel();
		}
	}

	private String translateComposerError(String errorMessage)
	{
		Resources resources = getResources();
		if (VCardComposer.FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO
				.equals(errorMessage))
		{
			return resources
					.getString(R.string.composer_failed_to_get_database_infomation);
		} else if (VCardComposer.FAILURE_REASON_NO_ENTRY.equals(errorMessage))
		{
			return resources
					.getString(R.string.composer_has_no_exportable_contact);
		} else if (VCardComposer.FAILURE_REASON_NOT_INITIALIZED
				.equals(errorMessage))
		{
			return resources.getString(R.string.composer_not_initialized);
		} else
		{
			return errorMessage;
		}
	}

	@Override
	protected void onCreate(Bundle bundle)
	{
		super.onCreate(bundle);

		mTargetDirectory = getString(R.string.config_export_dir);
		mFileNamePrefix = getString(R.string.config_export_file_prefix);
		mFileNameSuffix = getString(R.string.config_export_file_suffix);
		mFileNameExtension = getString(R.string.config_export_file_extension);
		mVCardTypeStr = getString(R.string.config_export_vcard_type);

		mExtensionsToConsider = new HashSet<String>();
		mExtensionsToConsider.add(mFileNameExtension);

		final String additionalExtensions = getString(R.string.config_export_extensions_to_consider);
		if (!TextUtils.isEmpty(additionalExtensions))
		{
			for (String extension : additionalExtensions.split(","))
			{
				String trimed = extension.trim();
				if (trimed.length() > 0)
				{
					mExtensionsToConsider.add(trimed);
				}
			}
		}

		final Resources resources = getResources();
		mFileIndexMinimum = resources
				.getInteger(R.integer.config_export_file_min_index);
		mFileIndexMaximum = resources
				.getInteger(R.integer.config_export_file_max_index);

		startExportVCardToSdCard();
	}

	@Override
	protected Dialog onCreateDialog(int id)
	{
		switch (id)
		{
		case R.id.dialog_export_confirmation:
		{
			return getExportConfirmationDialog();
		}
		case R.string.fail_reason_too_many_vcard:
		{
			return new AlertDialog.Builder(this).setTitle(
					R.string.exporting_contact_failed_title).setMessage(
					getString(R.string.exporting_contact_failed_message,
							getString(R.string.fail_reason_too_many_vcard)))
					.setPositiveButton(android.R.string.ok, mCancelListener)
					.create();
		}
		case R.id.dialog_fail_to_export_with_reason:
		{
			return getErrorDialogWithReason();
		}
		case R.id.dialog_sdcard_not_found:
		{
			AlertDialog.Builder builder = new AlertDialog.Builder(this)
					.setTitle(R.string.no_sdcard_title).setIcon(
							android.R.drawable.ic_dialog_alert).setMessage(
							R.string.no_sdcard_message).setPositiveButton(
							android.R.string.ok, mCancelListener);
			return builder.create();
		}
		case R.id.dialog_exporting_vcard:
		{
			if (mProgressDialog == null)
			{
				String title = getString(R.string.exporting_contact_list_title);
				String message = getString(
						R.string.exporting_contact_list_message,
						mExportingFileName);
				mProgressDialog = new ProgressDialog(ExportVCardActivity.this);
				mProgressDialog.setTitle(title);
				mProgressDialog.setMessage(message);
				mProgressDialog
						.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
				mProgressDialog.setOnCancelListener(mActualExportThread);
				mActualExportThread.start();
			}
			return mProgressDialog;
		}
		}
		return super.onCreateDialog(id);
	}

	@Override
	protected void onPrepareDialog(int id, Dialog dialog)
	{
		if (id == R.id.dialog_fail_to_export_with_reason)
		{
			((AlertDialog) dialog).setMessage(getErrorReason());
		} else if (id == R.id.dialog_export_confirmation)
		{
			((AlertDialog) dialog).setMessage(getString(
					R.string.confirm_export_message, mTargetFileName));
		} else
		{
			super.onPrepareDialog(id, dialog);
		}
	}

	@Override
	protected void onStop()
	{
		super.onStop();
		if (mActualExportThread != null)
		{
			// The Activity is no longer visible. Stop the thread.
			mActualExportThread.cancel();
			mActualExportThread = null;
		}

		if (!isFinishing())
		{
			finish();
		}
	}

	/**
	 * Tries to start exporting VCard. If there's no SDCard available,
	 * an error dialog is shown.
	 */
	public void startExportVCardToSdCard()
	{
		File targetDirectory = new File(mTargetDirectory);

		if (!(targetDirectory.exists() && targetDirectory.isDirectory() && targetDirectory
				.canRead())
				&& !targetDirectory.mkdirs())
		{
			showDialog(R.id.dialog_sdcard_not_found);
		} else
		{
			mTargetFileName = getAppropriateFileName(mTargetDirectory);
			if (TextUtils.isEmpty(mTargetFileName))
			{
				mTargetFileName = null;
				// finish() is called via the error dialog. Do not call the
				// method here.
				return;
			}

			showDialog(R.id.dialog_export_confirmation);
		}
	}

	/**
	 * Tries to get an appropriate filename. Returns null if it fails.
	 */
	private String getAppropriateFileName(final String destDirectory)
	{
		int fileNumberStringLength = 0;
		{
			// Calling Math.Log10() is costly.
			int tmp;
			for (fileNumberStringLength = 0, tmp = mFileIndexMaximum; tmp > 0; fileNumberStringLength++, tmp /= 10)
			{
			}
		}
		String bodyFormat = "%s%0" + fileNumberStringLength + "d%s";

		if (!ALLOW_LONG_FILE_NAME)
		{
			String possibleBody = String.format(bodyFormat, mFileNamePrefix, 1,
					mFileNameSuffix);
			if (possibleBody.length() > 8 || mFileNameExtension.length() > 3)
			{
				Log.e(LOG_TAG, "This code does not allow any long file name.");
				mErrorReason = getString(
						R.string.fail_reason_too_long_filename, String.format(
								"%s.%s", possibleBody, mFileNameExtension));
				showDialog(R.id.dialog_fail_to_export_with_reason);
				// finish() is called via the error dialog. Do not call the
				// method here.
				return null;
			}
		}

		// Note that this logic assumes that the target directory is case
		// insensitive.
		// As of 2009-07-16, it is true since the external storage is only
		// sdcard, and
		// it is formated as FAT/VFAT.
		// TODO: fix this.
		for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++)
		{
			boolean numberIsAvailable = true;
			// SD Association's specification seems to require this feature,
			// though we cannot
			// have the specification since it is proprietary...
			String body = null;
			for (String possibleExtension : mExtensionsToConsider)
			{
				body = String.format(bodyFormat, mFileNamePrefix, i,
						mFileNameSuffix);
				File file = new File(String.format("%s/%s.%s", destDirectory,
						body, possibleExtension));
				if (file.exists())
				{
					numberIsAvailable = false;
					break;
				}
			}
			if (numberIsAvailable)
			{
				return String.format("%s/%s.%s", destDirectory, body,
						mFileNameExtension);
			}
		}
		showDialog(R.string.fail_reason_too_many_vcard);
		return null;
	}

	public Dialog getExportConfirmationDialog()
	{
		if (TextUtils.isEmpty(mTargetFileName))
		{
			Log.e(LOG_TAG, "Target file name is empty, which must not be!");
			// This situation is not acceptable (probably a bug!), but we don't
			// have no reason to
			// show...
			mErrorReason = null;
			return getErrorDialogWithReason();
		}

		return new AlertDialog.Builder(this).setTitle(
				R.string.confirm_export_title).setMessage(
				getString(R.string.confirm_export_message, mTargetFileName))
				.setPositiveButton(android.R.string.ok,
						new ExportConfirmationListener(mTargetFileName))
				.setNegativeButton(android.R.string.cancel, mCancelListener)
				.setOnCancelListener(mCancelListener).create();
	}

	public Dialog getErrorDialogWithReason()
	{
		if (mErrorReason == null)
		{
			Log.e(LOG_TAG, "Error reason must have been set.");
			mErrorReason = getString(R.string.fail_reason_unknown);
		}
		return new AlertDialog.Builder(this).setTitle(
				R.string.exporting_contact_failed_title).setMessage(
				getString(R.string.exporting_contact_failed_message,
						mErrorReason)).setPositiveButton(android.R.string.ok,
				mCancelListener).setOnCancelListener(mCancelListener).create();
	}

	public void cancelExport()
	{
		if (mActualExportThread != null)
		{
			mActualExportThread.cancel();
			mActualExportThread = null;
		}
	}

	public String getErrorReason()
	{
		return mErrorReason;
	}

//	public static void setProgressNumberFormat(Class pdClass, String format)
//	{
//		try
//		{
//			Method createMethod = pdClass.getMethod("setProgressNumberFormat");
//			createMethod.invoke(format);
//		} catch (Exception e)
//		{
//			Log.e(LOG_TAG, "reflects progressDialog method setProgressNumberFormat error:" + e);
//		}
//	}
}
